﻿using System;
using System.Collections;
using System.Diagnostics;
using System.Reflection;
using System.Xml.Serialization;

using Gonte.Reflection;

namespace Gonte.Xml.Serialization
{
    public class XmlCollectionDeserializer
        : XmlObjectDeserializer
    {
        #region Constructors

        protected internal XmlCollectionDeserializer(Object @object)
            : base(@object)
        {
        }

        #endregion

        #region Overridables

        protected override bool ProcessElement()
        {
            OnElement();

            // Collections don't have attributes

            if (Reader.IsEmptyElement)
            {
                if (!OnEndElement())
                {
                    return false;
                }
            }

            return true;
        }

        protected override bool OnElement()
        {
            IncrementLevel();

            string elementName = Reader.LocalName;

            if (string.IsNullOrEmpty(rootElementName_)) // Root element
            {
                //VerifyElementNameMatchesObjectType(elementName);
                rootElementName_ = elementName;
            }
            else
            {
                // Create the item
                Object item = CreateItem();

                // Get the deserializer
                XmlObjectDeserializer deserializer = XmlObjectDeserializer.GetDeserializer(item);

                // Create the context
                XmlSerializationContext context = new XmlSerializationContext();
                context.Parent = object_;
                if (context_ != null)
                {
                    context.Attributes = context_.Attributes;
                }

                // Deserialize the item
                item = deserializer.Deserialize(Reader, context);

                // Add it to the collection
                OnDeserializedItem(item);
            }

            return true;
        }

        protected virtual Object CreateItem()
        {
            XmlElementAttribute attribute = null;

            if (context_ != null
                && !string.IsNullOrEmpty(context_.PropertyName))
            {
                Object parent = context_.Parent;
                PropertyInfo propertyInfo = parent.GetType().GetProperty(context_.PropertyName);
                attribute = CustomAttribute.GetCustomAttribute<XmlElementAttribute>(propertyInfo, false);
            }

            Type type = ObjectFactory.GetItemType((ICollection)object_, attribute);
            return CreateItem(type);
        }

        protected virtual void OnDeserializedItem(Object item)
        {
            // ICollection does not have an Add method, therefore use reflection to call the add method
            Type type = object_.GetType();
            MethodInfo methodInfo = type.GetMethod("Add");

            if (methodInfo != null)
            {
                methodInfo.Invoke(object_, new Object[] { item });
            }
            else
            {
                // Add support for queues
                methodInfo = type.GetMethod("Enqueue");

                if (methodInfo != null)
                {
                    methodInfo.Invoke(object_, new Object[] { item });
                }
                else
                {
                    throw new InvalidOperationException("The method [Add] should exist for the collection or implement a custom deserializer");
                }
            }
        }

        #endregion

        #region Helpers

        protected Object CreateItem(Type type)
        {
            Object item;

            if (type.IsDefined(typeof(XmlConstructorParameterAttribute), false)) // Custom creation
            {
                Type[] typeArguments;
                Object[] args;
                GetConstructorArguments(type, out typeArguments, out args);
                item = ObjectFactory.CreateObject(type, typeArguments, args);
            }
            else
            {
                if (null != Reader["xsi:type"])
                {
                    type = Type.GetType(Reader["xsi:type"]);
                }

                item = ObjectFactory.CreateObject(type);
            }

            Debug.WriteLine(string.Format("Created collection item of type: {0}", type.FullName));

            return item;
        }

        private void GetConstructorArguments(Type type, out Type[] typeArguments, out Object[] args)
        {
            XmlConstructorParameterAttribute[] attributes = CustomAttribute.GetCustomAttributes<XmlConstructorParameterAttribute>(type, false);
            int length = attributes.Length;
            typeArguments = new Type[length];
            args = new Object[length];
            Object parent = null;

            if (context_ != null)
            {
                parent = context_.Parent;
            }

            for (int i = 0; i < length; ++i)
            {
                if (attributes[i].AttributeName.ToLower().Equals("@parent"))
                {
                    args[i] = parent;
                }
                else
                {
                    throw new NotImplementedException(); //TODO: Implement populationg from attributes
                }

                typeArguments[i] = attributes[i].ParameterType;
            }
        } 

        #endregion
    }
}
